package com.sikaryofficial.backend.message.consumer;

import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.sikaryofficial.backend.annotation.TenantFilter;
import com.sikaryofficial.backend.config.ActivityConfig;
import com.sikaryofficial.backend.constant.ActivityConstant;
import com.sikaryofficial.backend.constant.ActivityTypeEnum;
import com.sikaryofficial.backend.constant.MyStatisticsTypeEnum;
import com.sikaryofficial.backend.constant.PrizeTypeEnum;
import com.sikaryofficial.backend.domain.entity.Activity;
import com.sikaryofficial.backend.domain.entity.ActivityPrizeRelation;
import com.sikaryofficial.backend.domain.entity.ActivityResult;
import com.sikaryofficial.backend.domain.entity.Prize;
import com.sikaryofficial.backend.message.body.ActivityResultUserInfoMessage;
import com.sikaryofficial.backend.message.body.LotteryMessage;
import com.sikaryofficial.backend.message.product.ProductProcess;
import com.sikaryofficial.backend.service.IActivityPrizeRelationService;
import com.sikaryofficial.backend.service.IActivityResultService;
import com.sikaryofficial.backend.service.IActivityService;
import com.sikaryofficial.backend.service.IPointsDetailStrategy;
import com.sikaryofficial.backend.service.IPrizeService;
import com.sikaryofficial.backend.service.email.notice.GuildTaskNoticeService;
import com.sikaryofficial.backend.service.statistics.MyStatisticsEvent;
import com.sikaryofficial.backend.service.statistics.MyStatisticsProcessor;
import com.sikaryofficial.common.core.constant.BrandEnum;
import com.sikaryofficial.common.core.context.SecurityContextHolder;
import com.sikaryofficial.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.sikaryofficial.backend.constant.RedisCacheKey.ACTIVITY_ATTEND_RESULT_CACHE;
import static com.sikaryofficial.backend.constant.RedisCacheKey.ACTIVITY_LOCK_CACHE;

/**
 * 抽奖消息处理
 */
@Component
@Slf4j
public class ActivityLotteryProcess {
    @Autowired
    private IActivityService activityService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private IActivityPrizeRelationService relationService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ProductProcess productProcess;
    @Value("${hismk.backend.activity.attend.resultSaveDay}")
    private Long resultSaveDay;
    @Autowired
    private MyStatisticsProcessor myListener;
    @Autowired
    private IActivityResultService activityResultService;
    @Autowired
    private IPrizeService prizeService;
    @Resource
    private Map<String, IPointsDetailStrategy> pointsDetailStrategyMap;
    @Resource
    private ActivityConfig activityConfig;
    @Autowired
    private GuildTaskNoticeService noticeService;

    /**
     * 消息监听
     * <p>
     * 抽奖业务-消费流程
     *
     * @param message 消息
     */
    @StreamListener(MQConsumer.BINDINGS_LOTTERY_INPUT)
    @Transactional(rollbackFor = Exception.class)
    public void onActivityLotteryMessage(@Payload LotteryMessage message) {
        log.info("=========================################==================================");
        log.info("[onMessage][线程编号:{} 消息内容：{}]", Thread.currentThread().getId(), message);
        log.info("==========================###################===============================");
        // 设置品牌
        if (CharSequenceUtil.isBlank(message.getBrand())) {
            message.setBrand(BrandEnum.HISMK.getCode());
        }
        SecurityContextHolder.setBrand(message.getBrand());
        // 活动抢购
        doActivityAttend(message);
        // 清除品牌
        SecurityContextHolder.remove();
    }

    @TenantFilter
    private void doActivityAttend(LotteryMessage message) {
        // 查询已上架
        Activity activity = activityService.getOne(new LambdaQueryWrapper<Activity>()
                .eq(Activity::getActivityId, message.getActivityId())
                .eq(Activity::getDeletedVersion, 0L)
                .eq(Activity::getHasEffect, ActivityConstant.EFFECT_YES)
        );
        if (!activityCheck(activity)) {
            return;
        }
        // 如果未中奖，则扣减参与积分，并退出
        if (Objects.isNull(message.getPrizeId())) {
            // 3、活动参与积分扣减
            attendPointReduce(message, activity);
            return;
        }
        ActivityPrizeRelation relation = relationService.getOne(new LambdaQueryWrapper<ActivityPrizeRelation>()
                .eq(ActivityPrizeRelation::getActivityId, message.getActivityId())
                .eq(ActivityPrizeRelation::getActivityPrizeId, message.getActivityPrizeId())
                .eq(ActivityPrizeRelation::getDeletedVersion, 0L)
        );
        if (Objects.isNull(relation)) {
            log.error("活动没有奖项关系数据");
            return;
        }
        // 抢购前判定下库存
        String lockKey = MessageFormat.format(ACTIVITY_LOCK_CACHE, message.getActivityId());
        // 判断库存是否充足
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(2000, 2000, TimeUnit.MILLISECONDS)) {
                // 扣减库存
                attendProcess(message, relation, activity);
                log.info("realStock  be reduce.");
            }
        } catch (Exception e) {
            log.error("抢购失败，锁被占用，请稍后再试");
            log.error("异常：{}", e);
            Thread.currentThread().interrupt();
        } finally {
            // 释放互斥锁
            lock.unlock();
        }
    }

    /**
     * 抽奖处理
     *
     * @param message  消息
     * @param activity 活动对象
     * @param relation 活动奖项关联对象
     */
    private void attendProcess(LotteryMessage message, ActivityPrizeRelation relation, Activity activity) {
        Integer activityType = activity.getActivityType();
        Integer stock = relation.getStock();
        // 库存更新判定
        if (stock != -1) {
            // 1、更新库存
            boolean flag = relationService.update(new ActivityPrizeRelation().setStock(relation.getStock() - 1)
                            .setUpdatedBy(message.getUserId())
                            .setUpdatedTime(new Date()),
                    new LambdaQueryWrapper<ActivityPrizeRelation>()
                            .eq(ActivityPrizeRelation::getDeletedVersion, 0L)
                            .eq(ActivityPrizeRelation::getActivityPrizeId, relation.getActivityPrizeId())
                            .ge(ActivityPrizeRelation::getStock, 1)
            );
            if (!flag) {
                log.error("抢购失败，活动库存不足，请稍后再试");
                return;
            }
        }
        // 2、查询礼品信息
        Prize prize = prizeService.getOne(new LambdaQueryWrapper<Prize>()
                .eq(Prize::getPrizeId, message.getPrizeId())
                .eq(Prize::getDeletedVersion, 0L)
        );
        if (Objects.isNull(prize)) {
            log.error("==============================礼品信息为空==============");
            return;
        }
        PrizeTypeEnum prizeTypeEnum = PrizeTypeEnum.getByCode(prize.getPrizeType());
        // 3、活动参与积分扣减
        attendPointReduce(message, activity);
        switch (prizeTypeEnum) {
            case VIRTUALLY:
                // 5、虚拟奖品，直接增加积分
                processVirtuallyPrize(message, prize.getPoints(), activityType, ActivityConstant.INCREASE, activity);
                break;
            case PHYSICAL:
                // 6、实物礼品处理
                processPhysicalPrize(message, prize);
                break;
            case THANK_YOU:
                log.info("===========================THANK_YOU======================");
                break;
            default:
                log.error("==============================奖品类型错误===============");
                break;
        }
    }

    /**
     * 参与活动-积分扣减
     *
     * @param message
     * @param activity
     */
    private void attendPointReduce(LotteryMessage message, Activity activity) {
        Integer activityType = activity.getActivityType();
        Integer points = activity.getPoints();
        // 是否为特殊活动
        if (Boolean.TRUE.equals(isSpecialActivity(message))) {
            return;
        }
        // 3、参与抽奖活动-扣减所需积分
        processVirtuallyPrize(message, points, activityType, ActivityConstant.REDUCE, activity);
    }

    /**
     * 是否为特殊活动
     *
     * @param message
     * @return
     */
    private boolean isSpecialActivity(LotteryMessage message) {
        return Objects.equals(activityConfig.getSpecialActivityId(), message.getActivityId());
    }

    /**
     * 如果是 虚拟奖品，比如积分 ，则直接增加积分余额
     *
     * @param message 消息体对象
     * @Param opt reduce/increase
     */
    private void processVirtuallyPrize(LotteryMessage message, Integer points, Integer activityType, String opt, Activity activity) {
        // 添加积分
        if (Objects.isNull(activityType)) {
            log.error("===========================活动类型错误===============");
            return;
        }
        ActivityTypeEnum activityTypeEnum = ActivityTypeEnum.getNameByCode(activityType);
        if (Objects.isNull(activityTypeEnum)) {
            log.error("===========================活动类型错误===============");
            return;
        }
        String servicePrefix = activityTypeEnum.getName();
        if (CharSequenceUtil.isBlank(servicePrefix)) {
            log.error("===========================servicePrefix错误===============");
            return;
        }
        // 积分获取
        IPointsDetailStrategy pointsDetailStrategy = pointsDetailStrategyMap.get(servicePrefix + IPointsDetailStrategy.SUFFIX);
        // 2、虚拟物品积分-增加:  如果未中奖, 用活动ID代替奖品ID
        pointsDetailStrategy.updatePointsByInputValue(Objects.isNull(message.getPrizeId()) ? message.getActivityId() : message.getPrizeId(),
                message.getUserId(), points, opt, activity.getActivityName());
    }

    /**
     * 如果是实物礼品，则走如下流程：
     * 1、生成抢购结果
     * 2、生成抢购结果用户信息
     * 3、生成统计数据
     *
     * @param message 消息体对象
     */
    private void processPhysicalPrize(LotteryMessage message, Prize prize) {
        // 3、生成抢购结果
        Long activityResultId = IdWorker.getId();
        activityResultService.save(new ActivityResult()
                .setActivityResultId(activityResultId)
                .setActivityId(message.getActivityId())
                .setPrizeId(message.getPrizeId())
                .setCreatedBy(message.getUserId())
                .setCreatedName(message.getUserName())
                .setCreatedTime(new Date())
        );
        // 异步更新结果数据
        ActivityResultUserInfoMessage userInfoMessage = new ActivityResultUserInfoMessage();
        userInfoMessage.setActivityResultId(activityResultId);
        productProcess.sendActivityResultMessage(userInfoMessage);

        // 5、缓存抢购记录-缓存24小时
        redisService.setCacheObject(MessageFormat.format(ACTIVITY_ATTEND_RESULT_CACHE, message.getActivityId(), message.getUserId()), true
                , resultSaveDay, TimeUnit.DAYS);
        // 6、添加兑换数量统计
        myListener.addMyStatistics(new MyStatisticsEvent(MyStatisticsTypeEnum.RESERVATION_AWARDS_NUM.getBusinessType(), message.getUserId(), activityResultId));
        // 实物中奖，邮件发送
        noticeService.winEmailNotice(message.getUserId(), prize.getPrizeName(),prize.getHasCash());
    }

    /**
     * 活动相关检查
     *
     * @param activity 活动对象
     */
    private boolean activityCheck(Activity activity) {
        if (Objects.isNull(activity) || !Objects.equals(activity.getHasOnline(), ActivityConstant.ON_LINE)
                || !StringUtils.equalsAny(
                activity.getActivityType().toString(),
                ActivityTypeEnum.LOTTERY_TURNTABLE.getCode().toString(),
                ActivityTypeEnum.LOTTERY_SCRATCH.getCode().toString())
        ) {
            // 当前活动未上架或不在活动时间
            log.error("活动不存在 | 非线上活动 | 活动类型不正确 ");
            return false;
        }
        return true;
    }
}
